<?php
/**
 * @author Maxim Oransky
 * @author Alexander Makarov
 *
 * This code is branch of http://rmcreative.ru/article/programming/typograph/
 *
 * Thanks to:
 * - http://philigon.ru/
 * - http://artlebedev.ru/kovodstvo/
 * - http://pvt.livejournal.com/
 * - faZeful
 * - Naruvi
 * - Shaman
 * - Eagle
 * - grasshopper
 * - Max
 * - Reki
 * ------------------------------------------------------------
 */

/**
 * Usage:
 * $text = Typo::getInstance()->processText($text);
 *
 *
 * Usage with another strategy:
 *
 * class NonUtfTypo extends Typo {
 *     public static function getInstance(DefaultTypoStrategy $oStrategy = null) {
 *         return parent::getInstance(new MyNonUtfStrategy())
 *     }
 * }
 *
 * $text = NonUtfTypo::getInstance()->processText($text);
 *
 */
class Typo {

    private $arrReplaceRules;
    private $safeBlocksRegex;

    protected function __construct(DefaultTypoStrategy $oStrategy) {
        $this->arrReplaceRules = $oStrategy->getProcessRegexRules();
        $this->safeBlocksRegex = $oStrategy->getSafeBlocksRegex();
    }

    private static $hInstances = array();

    /**
     * @return Typo
     */
    public static function getInstance(DefaultTypoStrategy $oStrategy = null) {

        if (!$oStrategy) $oStrategy = new DefaultTypoStrategy();

        $strategyClassname = get_class($oStrategy);

        if (!isset(self::$hInstances[$strategyClassname])) {
            self::$hInstances[$strategyClassname] = new self($oStrategy);
        }

        return self::$hInstances[$strategyClassname];
    }

    public function processText($str) {

        if (!trim($str)) return '';

        $str = ' ' . $str . ' ';
        foreach ($this->arrReplaceRules as $arrRule) {
            list($arrFrom, $arrTo) = $arrRule;
            $str = preg_replace($arrFrom, $arrTo, $str);
        }

        return trim($str);
    }

    public function processHtml($str) {

        $oProtector = new TextBlockProtector();

        $str = $oProtector->protectText($str, $this->safeBlocksRegex);
        $str = $this->processText($str);
        $str = $oProtector->unprotectText($str);

        return $str;
    }
}

class TextBlockProtector {

    private $hStorage;

    public function protectText($text, $regex) {
        $this->hStorage = array();
        return preg_replace_callback($regex, array($this, 'add'), $text);
    }

    public function unprotectText($text) {
        return strtr($text, $this->hStorage);
    }

    private function add(array $arrMatches) {
        $key = '<tag' . count($this->hStorage) . '>';
        $this->hStorage[$key] = $arrMatches[0];
        return $key;
    }
}
